﻿using System;
using System.Configuration;
using System.Windows;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using SilverQuery;

namespace SilverPortal
{
	/// <summary>
	/// Creates instances of <see cref="SilverPortalModule"/>s.
	/// </summary>
	/// <remarks>Performs on-demand downloading of module assemblies.</remarks>
	public class ModuleFactory
	{
		private static Dictionary<string, Assembly> _loadedAssemblies = 
			new Dictionary<string,Assembly>();

		public static void CreateAsync(ModuleConfig config, ModuleCreatedCallback callback)
		{
			string assemblyName = config.TypeName.Substring(config.TypeName.IndexOf(',') + 1);
			
			Assembly assembly;
			if (!_loadedAssemblies.TryGetValue(assemblyName, out assembly))
			{
				// Download the assembly
				WebClient client = new WebClient();
				client.OpenReadCompleted += OnAssemblyDownloaded;
				client.OpenReadAsync(GetAssemblyUrl(assemblyName), new object[]{ config, callback});
			}
		}

		private static Uri GetAssemblyUrl(string assemblyName)
		{
			string baseUrl = SilverPortalComponent.CurrentUriPath();

			UriBuilder uri = new UriBuilder(baseUrl);
			uri.Path = "ClientBin/" + assemblyName + ".dll";
			if (baseUrl.IndexOf("localhost") != -1)
				uri.Query = "nocache=" + DateTime.Now.Ticks.ToString();

			return uri.Uri;
		}

		private static void OnAssemblyDownloaded(object sender, OpenReadCompletedEventArgs e)
		{
			var state = (object[])e.UserState;
			var config = (ModuleConfig)state[0];
			var callback = (ModuleCreatedCallback)state[1];

			if (e.Error != null)
			{
				callback(null, e.Error);
				return;
			}
				
			try
			{
				Assembly assembly = new AssemblyPart().Load(e.Result);
								
				string assemblyName = config.TypeName.Substring(config.TypeName.IndexOf(',') + 1);
				string className = config.TypeName.Substring(0, config.TypeName.IndexOf(','));
				_loadedAssemblies[assemblyName] = assembly;

				Type moduleType = null;

				foreach (Type type in assembly.GetTypes())
				{
					if (type.FullName == className)
					{
						moduleType = type;
						break;
					}
				}

				if (moduleType == null)
					throw new InvalidOperationException("Could not find type " + className + " in assembly " + assemblyName);

				var module = (SilverPortalModule)Activator.CreateInstance(moduleType);
				module.Config = config;

				callback(module, null);
			}
			catch (Exception error)
			{
				callback(null, error);
			}
		}
	}

	public delegate void ModuleCreatedCallback(SilverPortalModule module, Exception error);
}
